跳到主要内容

SpringSecurity 集成 LDAP

参考资料 单点登录(一)| LDAP 协议 参考资料 官方教程 Authenticating a User with LDAP

注意 SpringBoot 内置了一个 LDAP 服务,所以无需手动添加一个服务了

搭建一个简单的环境

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
</dependency>

<!-- 启动 spring security ldap 框架支持 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
</dependency>

<!-- UnboundID LDAP SDK for Java 是一个快速、综合易用的 LDAP 目录服务的 Java 客户端 API,
可读写 LDIF、使用BASE64 和 ASN.1 BER 进行编码解码,支持安全通信等特性
-->
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
</dependency>

这里使用的 unboundid 本身就是一个服务器

创建一个 Controller

先随便创建

@RestController
public class HomeController {

@GetMapping("/")
public String index() {
return "Welcome to the home page!";
}

}

编写配置端点 ⭐

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin();
}

// 这里就是核心了
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
// 如果用户位于目录中的固定位置(可以直接从用户名计算出DN,而无需进行目录搜索),
// 则可以使用此属性直接映射到 DN。 它直接映射到 AbstractLdapAuthenticator 的 userDnPatterns属性。
// 该值是用于构建用户 DN的特定模式,例如 “ uid={0},ou=people”。
// 这里的“ {0}” 必须存在,它是一个用户名占位符。
.userDnPatterns("uid={0},ou=people")
// 指定搜索哪个 ou 的成员(例如,这里搜索 ou为“groups” 的成员)
.groupSearchBase("ou=groups")
// 这个 contextSource 默认指向嵌入式的 LDAP服务器
.contextSource()
.url("ldap://localhost:8389/dc=springframework,dc=org")
.and()
.passwordCompare()
.passwordEncoder(new BCryptPasswordEncoder())
.passwordAttribute("userPassword");
}

}

一般还需要一个 LDAP 服务器。但是 Spring boot 提供了一个自动配置的、纯 Java编写的嵌入式服务器,使用这个 ldapAuthentication() 方法配置登录表单的用户名插入 {0} 的位置,以便在 LDAP 服务器中搜索 uid={0},ou=people,dc=springframework,dc=org。此外 passwordCompare() 方法还配置编码器和密码属性的名称。

添加数据

参考资料 LDAP学习笔记 - LDIF

初始状态下,LDAP是一个空目录,即没有任何数据。可通过程序代码向目录数据库中添加数据,也可使用OpenLDAP客户端工具 ldapadd 命令来完成添加数据的操作,该命令可将一个 LDIF 文件中的条目添加到目录。因此,需要首先创建一个 LDIF 文件,然后再进行添加操作。

LDAP 服务器可以使用 LDIF(LDAP 数据交换格式)文件来交换用户数据。application.yml 中的 spring.ldap.embedded.ldif 属性允许 SpringBoot 导入 LDIF 数据文件,这很方便预加载模拟数据。

这里创建一个 test-server.ldif

dn: dc=springframework,dc=org
objectclass: top
objectclass: domain
objectclass: extensibleObject
dc: springframework

dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups

dn: ou=subgroups,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: subgroups

dn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: people

dn: ou=space cadets,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: space cadets

dn: ou=\"quoted people\",dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: "quoted people"

dn: ou=otherpeople,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: otherpeople

dn: uid=ben,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Ben Alex
sn: Alex
uid: ben
userPassword: $2a$10$c6bSeWPhg06xB1lvmaWNNe4NROmZiSpYhlocU/98HNr2MhIOiSt36

dn: uid=bob,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Bob Hamilton
sn: Hamilton
uid: bob
userPassword: bobspassword

dn: uid=joe,ou=otherpeople,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Joe Smeth
sn: Smeth
uid: joe
userPassword: joespassword

dn: cn=mouse\, jerry,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Mouse, Jerry
sn: Mouse
uid: jerry
userPassword: jerryspassword

dn: cn=slash/guy,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: slash/guy
sn: Slash
uid: slashguy
userPassword: slashguyspassword

dn: cn=quote\"guy,ou=\"quoted people\",dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: quote\"guy
sn: Quote
uid: quoteguy
userPassword: quoteguyspassword

dn: uid=space cadet,ou=space cadets,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Space Cadet
sn: Cadet
uid: space cadet
userPassword: spacecadetspassword



dn: cn=developers,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfUniqueNames
cn: developers
ou: developer
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org
uniqueMember: uid=bob,ou=people,dc=springframework,dc=org

dn: cn=managers,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfUniqueNames
cn: managers
ou: manager
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org
uniqueMember: cn=mouse\, jerry,ou=people,dc=springframework,dc=org

dn: cn=submanagers,ou=subgroups,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfUniqueNames
cn: submanagers
ou: submanager
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org

编写配置文件

spring:
ldap:
embedded: # 嵌入式 ldap 服务的设置
port: 8389 # 设置这个嵌入式的 LDAP 的端口
ldif: "classpath:test-server.ldif" # 启动后加载的 ldif
base-dn: "dc=springframework,dc=org" # 目录树的最顶部就是 base-dn,可以理解为创建一个根用户

启动测试

启动后,输入用户名 ben 和密码 bensapsword 就能看到欢迎页面了

总的项目目录